#include <windows.h>
#include <wininet.h>
#include <stdarg.h>

typedef struct
{
    HANDLE hHeap;
    HANDLE hStdOut;
    HANDLE hStdErr;
} Handles;

void __forceinline Handles_init(Handles *hs)
{
    hs->hHeap = GetProcessHeap();
    hs->hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    hs->hStdErr = GetStdHandle(STD_ERROR_HANDLE);
}

void Write(HANDLE h, LPVOID s, DWORD n) { WriteFile(h, s, n, &n, NULL); }
void PrintExA(HANDLE h, LPCSTR s, DWORD n) { WriteConsoleA(h, s, n, NULL, NULL); }
void PrintExW(HANDLE h, LPCWSTR s, DWORD n) { WriteConsoleW(h, s, n, NULL, NULL); }
void PrintfA(HANDLE h, LPCSTR fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    LPSTR s;
    DWORD dwSize = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, fmt, 0, 0, (LPSTR)&s, 0, &ap);
    if (s)
    {
        PrintExA(h, s, dwSize);
        LocalFree(s);
    }
    va_end(ap);
}
void PrintfW(HANDLE h, LPCWSTR fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    LPWSTR s;
    DWORD dwSize = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, fmt, 0, 0, (LPWSTR)&s, 0, &ap);
    if (s)
    {
        PrintExW(h, s, dwSize);
        LocalFree(s);
    }
    va_end(ap);
}

#define PrintA(h, s) PrintExA(h, s, sizeof(s) - 1)
#define PrintW(h, s) PrintExW(h, s, sizeof(s) - 1)
#define Print(h, s) PrintEx(h, s, sizeof(s) - 1)

#ifndef _DEBUG
#define ERR(fun, code) ((void)0)
#define E(fun) ((void)0)
#else
#define ERR(fun, code) ErrorMessage(TEXT(__FILE__), __LINE__, TEXT(__FUNCSIG__), TEXT(fun), code)
#define E(fun)                     \
    do                             \
    {                              \
        DWORD dw = GetLastError(); \
        if (dw)                    \
            ERR(fun, dw);          \
    } while (0)
void ErrorMessage(LPCTSTR lpszFileName, DWORD dwLine, LPCTSTR lpszParentFunction, LPCTSTR lpszFunction, DWORD dwCode); // 输出错误信息
#endif

#if defined(UNICODE) || defined(_UNICODE)
#define PrintEx PrintExW
#define Printf PrintfW
#else
#define PrintEx PrintExA
#define Printf PrintfA
#endif

void PrintNetErr(Handles *h);

void mainCRTStartup()
{
    Handles hs;
    Handles_init(&hs);
    int argc;
    LPCWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
    if (!argv)
        E("CommandLineToArgvW");
    else
    {
        if (argc < 2 || argc > 3)
            PrintfW(hs.hStdErr, L"usage: %s url [output filepath]\n", argv[0]);
        else
        {
            HANDLE hFile = hs.hStdOut;
            if (argc == 3)
            {
                hFile = CreateFileW(argv[2], GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
                if (INVALID_HANDLE_VALUE == hFile)
                    E("CreateFileW");
            }
            if (hFile != INVALID_HANDLE_VALUE)
            {
                HINTERNET hOpen = InternetOpenA("", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
                if (!hOpen)
                    E("InternetOpenA");
                else
                {
                    HINTERNET hUrl = InternetOpenUrlW(hOpen, argv[1], NULL, (DWORD)-1, 0, 0);
                    if (!hUrl)
                    {
                        E("InternetOpenUrlW");
                        PrintNetErr(&hs);
                    }
                    else
                    {
                        DWORD dwSize = 0;
                        do
                        {
                            if (!InternetQueryDataAvailable(hUrl, &dwSize, 0, 0))
                                E("InternetQueryDataAvailable");
                            else
                            {
                                char *buffer = HeapAlloc(hs.hHeap, 0, dwSize);
                                DWORD dwRead;
                                if (!InternetReadFile(hUrl, buffer, dwSize, &dwRead))
                                {
                                    E("InternetReadFile");
                                    dwSize = 0;
                                }
                                else
                                    Write(hFile, buffer, dwRead);
                                HeapFree(hs.hHeap, 0, buffer);
                            }
                        } while (dwSize);
                        if(argc == 2)
                            PrintA(hs.hStdOut, "\n");
                        if (!InternetCloseHandle(hUrl))
                            E("InternetCloseHandle");
                    }
                    if (!InternetCloseHandle(hOpen))
                        E("InternetCloseHandle");
                }
                if(argc == 3)
                    CloseHandle(hFile);
            }
        }
        LocalFree(argv);
    }
    ExitProcess(0);
}

void PrintNetErr(Handles *h)
{
    DWORD dwLen = 0;
    DWORD dwError = 0;
    InternetGetLastResponseInfoA(NULL, NULL, &dwLen);
    char *s = HeapAlloc(h->hHeap, 0, dwLen + 1);
    InternetGetLastResponseInfoA(&dwError, s, &dwLen);
    PrintfA(h->hStdErr, "error: %1!lu!, buffer: %2\n", dwError, s);
    HeapFree(h->hHeap, 0, s);
}

#ifdef _DEBUG
void ErrorMessage(LPCTSTR lpszFileName, DWORD dwLine, LPCTSTR lpszParentFunction, LPCTSTR lpszFunction, DWORD dwCode)
{
    LPTSTR lpMsgBuf = NULL;
    LPTSTR lpDisplayBuf = NULL;
    DWORD dwSize = 0;

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS |
            FORMAT_MESSAGE_MAX_WIDTH_MASK,
        NULL,
        dwCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL);

#ifdef __cplusplus
    using va_list_array = va_list[];
#else
#define va_list_array (va_list[])
#endif

    if (lpMsgBuf)
        dwSize = FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_ARGUMENT_ARRAY |
            FORMAT_MESSAGE_FROM_STRING,
            TEXT("Filename:%1\n")
            TEXT("Line:%2!lu!\n")
            TEXT("Parent function:%3\n")
            TEXT("Function:%4\n")
            TEXT("Code:%5!lu!\n")
            TEXT("Description:%6\n"),
            0,
            0,
            (LPTSTR)&lpDisplayBuf,
            0,
            va_list_array{
                (va_list)lpszFileName,
                (va_list)(SIZE_T)dwLine,
                (va_list)lpszParentFunction,
                (va_list)lpszFunction,
                (va_list)(SIZE_T)dwCode,
                (va_list)lpMsgBuf
            }
        );

    // Display the error message and exit the process
    if (lpDisplayBuf && dwSize)
        WriteConsole(GetStdHandle(STD_ERROR_HANDLE), lpDisplayBuf, dwSize, NULL, NULL);

    if (lpMsgBuf)
        LocalFree(lpMsgBuf);
    if (lpDisplayBuf)
        LocalFree(lpDisplayBuf);
}
#endif